const http = require('http');
const fs = require('fs');
const fsp = fs.promises;
const path = require('path');
const net = require('net');
const { exec } = require('child_process');
const os = require('os');

const UPLOAD_DIR = path.join(__dirname, 'uploads');
const TMP_DIR = path.join(UPLOAD_DIR, '../tmp');

async function ensureDirs() {
  await fsp.mkdir(TMP_DIR, { recursive: true });
}

function setCorsHeaders(res) {
  res.setHeader('Access-Control-Allow-Origin', '*');
  res.setHeader('Access-Control-Allow-Methods', 'GET,POST,OPTIONS');
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
}

// sanitize filename to avoid path traversal
function safeFilename(name) {
  return name
  return path.basename(name).replace(/[^a-zA-Z0-9-_.]/g, '_');
}

async function handleUpload(req, res, query) {
  // expected query: filename, chunkIndex, totalChunks
  const filename = safeFilename(query.filename || 'unnamed');
  const chunkIndex = Number(query.chunkIndex);
  const totalChunks = Number(query.totalChunks);
  if (!Number.isFinite(chunkIndex) || !Number.isFinite(totalChunks)) {
    res.writeHead(400, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ error: 'chunkIndex and totalChunks required' }));
    return;
  }

  const partPath = path.join(TMP_DIR, `${filename}.part-${chunkIndex}`);
  // stream request body to file
  const writeStream = fs.createWriteStream(partPath);
  req.pipe(writeStream);

  writeStream.on('error', (err) => {
    console.error('writeStream error', err);
    res.writeHead(500, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ error: 'write failed' }));
  });

  writeStream.on('finish', async () => {
    var isComplete = false;
    try {
      // 检查是否所有分片都已上传
      const allExist = await Promise.all(
        Array.from({ length: totalChunks }, (_, i) =>
          fsp.access(path.join(TMP_DIR, `${filename}.part-${i}`))
            .then(() => true)
            .catch(() => false)
        )
      );
      const ready = allExist.every(Boolean);
      if (ready) {
        // === 合并文件 ===
        let finalPath = path.join(UPLOAD_DIR, filename);

        // 如果有重复文件名，则加时间戳
        if (fs.existsSync(finalPath)) {
          const ext = path.extname(filename);
          const base = path.basename(filename, ext);
          const timestamp = Date.now();
          const newName = `${base}_${timestamp}${ext}`;
          finalPath = path.join(UPLOAD_DIR, newName);
        }

        const tmpMergePath = finalPath + '.merging';
        try {
          await fsp.access(tmpMergePath);
          // merging already in progress
        } catch (e) {
          // 创建 lock 文件避免并发合并
          await fsp.writeFile(tmpMergePath, '');
          const out = fs.createWriteStream(finalPath);
          try {
            for (let i = 0; i < totalChunks; i++) {
              const part = path.join(TMP_DIR, `${filename}.part-${i}`);
              await new Promise((resolve, reject) => {
                const rs = fs.createReadStream(part);
                rs.on('error', reject);
                rs.on('end', async () => {
                  try { await fsp.unlink(part); } catch (_) { }
                  resolve();
                });
                rs.pipe(out, { end: false });
              });
            }
            out.end();
            await fsp.unlink(tmpMergePath).catch(() => { });
            console.log('Merged file:', finalPath);
            isComplete = true;
          } catch (mergeErr) {
            console.error('merge error', mergeErr);
            await fsp.unlink(tmpMergePath).catch(() => { });
          }
        }
      }

      res.writeHead(200, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify({ ok: true, isComplete }));
    } catch (e) {
      console.error(e);
      res.writeHead(500, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify({ error: 'server error' }));
    }
  });
}




(async function init() {
  await ensureDirs();
  const server = http.createServer(async (req, res) => {
    setCorsHeaders(res);
    if (req.method === 'OPTIONS') {
      res.writeHead(204);
      res.end();
      return;
    }

    const url = new URL(req.url, `http://${req.headers.host}`);
    const pathname = url.pathname;
    const query = Object.fromEntries(url.searchParams.entries());

    try {
      if (req.method === 'POST' && pathname === '/upload') {
        await handleUpload(req, res, query);
        return;
      }
      // 处理静态文件请求 (public目录)
      if (req.url.startsWith('/assets/')) {
        console.log('🟩 🟩 🟩 req.url', req.url);
        const filePath = path.join(__dirname, req.url);
        fs.readFile(filePath, (err, data) => {
          if (err) {
            res.writeHead(404);
            res.end('Not Found');
            return;
          }

          // 根据文件扩展名设置Content-Type
          const ext = path.extname(filePath);
          let contentType = 'text/plain';
          if (ext === '.js') contentType = 'text/javascript';
          else if (ext === '.css') contentType = 'text/css';
          else if (ext === '.html') contentType = 'text/html';

          res.writeHead(200, { 'Content-Type': contentType });
          res.end(data);
        });
        return;
      }
      if (req.url.startsWith('/list-files')) {

        // 从查询参数获取目标目录
        const query = new URL(req.url, `http://${req.headers.host}`).searchParams;
        const targetDir = query.get('dir') || 'uploads';

        // 验证目录名称
        if (!/^[a-zA-Z0-9_-]+$/.test(targetDir)) {
          res.writeHead(400, { 'Content-Type': 'application/json' });
          res.end(JSON.stringify({ error: 'Invalid directory name' }));
          return;
        }

        const dirPath = path.join(__dirname, targetDir);

        // 递归读取目录中的所有文件
        console.log(`尝试读取目录: ${dirPath}`); // 调试日志

        const readDirRecursive = (dir, relativePath = '') => {
          let results = [];
          const files = fs.readdirSync(dir);

          for (const file of files) {
            if (file.startsWith('.')) continue; // 跳过隐藏文件

            const fullPath = path.join(dir, file);
            const stats = fs.statSync(fullPath);
            const relativeFilePath = relativePath ? `${relativePath}/${file}` : file;

            if (stats.isDirectory()) {
              // 添加文件夹信息
              results.push({
                name: relativeFilePath,
                isDirectory: true,
                size: 0,
                lastModified: stats.mtime.toISOString()
              });
              // 递归处理子目录
              results = results.concat(readDirRecursive(fullPath, relativeFilePath));
            } else {
              // 添加文件信息
              results.push({
                name: relativeFilePath, // 使用相对路径作为文件名
                isDirectory: false,
                size: stats.size,
                lastModified: stats.mtime.toISOString()
              });
            }
          }

          return results;
        };

        try {
          // 检查目录是否存在，不存在则创建
          if (!fs.existsSync(dirPath)) {
            fs.mkdirSync(dirPath, { recursive: true });
            console.log(`已创建目录: ${dirPath}`);
            res.writeHead(200, { 'Content-Type': 'application/json' });
            return res.end(JSON.stringify([]));
          }

          // 获取所有文件（递归）
          const fileDetails = readDirRecursive(dirPath);

          res.writeHead(200, { 'Content-Type': 'application/json' });
          res.end(JSON.stringify(fileDetails));
        } catch (err) {
          console.error(`处理目录 ${dirPath} 失败:`, err);
          res.writeHead(500, { 'Content-Type': 'application/json' });
          res.end(JSON.stringify({
            error: '无法读取文件列表',
            details: err.message,
            code: err.code
          }));
        }

        return
      }
      if (req.url.startsWith('/download/')) {
        // 处理文件下载请求
        const query = new URL(req.url, `http://${req.headers.host}`).searchParams;

        const filename = decodeURIComponent(req.url.slice('/download/'.length).split('?')[0]);
        const targetDir = query.get('dir') || 'uploads';

        // 验证目录名称
        if (!/^[a-zA-Z0-9_-]+$/.test(targetDir)) {
          res.writeHead(400, { 'Content-Type': 'text/plain' });
          res.end('Invalid directory name');
          return;
        }

        const dirPath = path.join(__dirname, targetDir);
        const filePath = path.join(dirPath, filename);

        // 检查文件是否存在
        fs.stat(filePath, (err, stats) => {
          if (err || !stats.isFile()) {
            res.writeHead(404, { 'Content-Type': 'text/plain' });
            res.end('文件不存在');
            return;
          }

          // 设置响应头 - 兼容iOS Safari的文件名编码
          const userAgent = req.headers['user-agent'] || '';
          const isIOS = /iphone|ipad|ipod/i.test(userAgent.toLowerCase());

          let contentDisposition;
          if (isIOS) {
            // iOS Safari需要RFC 5987编码
            const encodedFilename = encodeURIComponent(filename).replace(/['()]/g, encodeURIComponent).replace(/\*/g, '%2A');
            contentDisposition = `attachment; filename*=UTF-8''${encodedFilename}`;
          } else {
            // 其他浏览器使用标准编码
            contentDisposition = `attachment; filename="${encodeURIComponent(filename)}"`;
          }

          // 根据文件扩展名设置Content-Type
          const ext = path.extname(filePath).toLowerCase();
          const contentTypeMap = {
            // 图片
            '.jpg': 'image/jpeg',
            '.jpeg': 'image/jpeg',
            '.png': 'image/png',
            '.gif': 'image/gif',
            '.webp': 'image/webp',
            '.svg': 'image/svg+xml',
            '.bmp': 'image/bmp',
            '.ico': 'image/x-icon',

            // 视频
            '.mp4': 'video/mp4',
            '.webm': 'video/webm',
            '.ogg': 'video/ogg',
            '.avi': 'video/x-msvideo',
            '.mov': 'video/quicktime',
            '.wmv': 'video/x-ms-wmv',

            // 音频
            '.mp3': 'audio/mpeg',
            '.wav': 'audio/wav',
            '.ogg': 'audio/ogg',
            '.m4a': 'audio/mp4',
            '.flac': 'audio/flac',

            // 文档
            '.pdf': 'application/pdf',
            '.doc': 'application/msword',
            '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
            '.xls': 'application/vnd.ms-excel',
            '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
            '.ppt': 'application/vnd.ms-powerpoint',
            '.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
            '.csv': 'text/csv',
            '.txt': 'text/plain',
            '.rtf': 'application/rtf',

            // 压缩包
            '.zip': 'application/zip',
            '.rar': 'application/x-rar-compressed',
            '.7z': 'application/x-7z-compressed',
            '.tar': 'application/x-tar',
            '.gz': 'application/gzip',

            // 代码
            '.js': 'application/javascript',
            '.json': 'application/json',
            '.html': 'text/html',
            '.htm': 'text/html',
            '.css': 'text/css',
            '.xml': 'application/xml',

            // 可执行文件
            '.exe': 'application/octet-stream',
            '.msi': 'application/octet-stream',
            '.dmg': 'application/octet-stream',
            '.pkg': 'application/octet-stream',
            '.deb': 'application/octet-stream',
            '.rpm': 'application/octet-stream'
          };

          const contentType = contentTypeMap[ext] || 'application/octet-stream';

          // 对于可执行文件，添加额外的安全头
          const headers = {
            'Content-Type': contentType,
            'Content-Disposition': contentDisposition,
            'Content-Length': stats.size,
            'X-Content-Type-Options': 'nosniff' // 防止MIME类型嗅探
          };

          // 如果是可执行文件，添加更多安全头
          if (['.exe', '.msi', '.dmg', '.pkg', '.deb', '.rpm'].includes(ext)) {
            headers['Content-Security-Policy'] = "default-src 'none'";
            headers['X-Download-Options'] = 'noopen'; // 防止IE直接执行
          }

          res.writeHead(200, headers);

          // 创建读取流并将其管道连接到响应
          const fileStream = fs.createReadStream(filePath);
          fileStream.pipe(res);

          // 处理错误
          fileStream.on('error', (error) => {
            console.error(`下载文件 ${filename} 时出错:`, error);
            res.end();
          });
        });
        return
      }
      if (req.method === 'GET' && pathname === '/') {
        fs.readFile(path.join(__dirname, 'index.html'), (err, data) => {
          if (err) {
            res.writeHead(500, { 'Content-Type': 'text/plain' });
            res.end('Internal Server Error');
            return;
          }

          res.writeHead(200, { 'Content-Type': 'text/html' });
          res.end(data);
        });
        return;
      }

      // 添加对 db.json 的访问支持
      if (req.method === 'GET' && pathname === '/db.json') {
        fs.readFile(path.join(__dirname, 'db.json'), (err, data) => {
          if (err) {
            res.writeHead(404, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({ error: 'db.json not found' }));
            return;
          }

          res.writeHead(200, { 'Content-Type': 'application/json' });
          res.end(data);
        });
        return;
      }

      // 添加修改 db.json 的路由
      if (req.method === 'POST' && pathname === '/update-db') {
        let body = '';
        req.on('data', chunk => {
          body += chunk.toString();
        });
        req.on('end', () => {
          try {
            const newData = JSON.parse(body);
            // 读取现有的 db.json 文件
            fs.readFile(path.join(__dirname, 'db.json'), 'utf8', (err, data) => {
              if (err) {
                res.writeHead(500, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify({ error: 'Failed to read db.json' }));
                return;
              }

              // 解析现有数据
              const dbData = JSON.parse(data);

              // 更新 msg 字段的值
              dbData.msg = newData.msg;

              // 写入更新后的数据
              fs.writeFile(path.join(__dirname, 'db.json'), JSON.stringify(dbData, null, 2), (err) => {
                if (err) {
                  res.writeHead(500, { 'Content-Type': 'application/json' });
                  res.end(JSON.stringify({ error: 'Failed to write db.json' }));
                  return;
                }

                res.writeHead(200, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify({ success: true }));
              });
            });
          } catch (e) {
            res.writeHead(400, { 'Content-Type': 'application/json' });
            res.end(JSON.stringify({ error: 'Invalid JSON' }));
          }
        });
        return;
      }
      res.writeHead(404);
      res.end('Not found');
    } catch (e) {
      console.error(e);
      res.writeHead(500, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify({ error: 'server exception' }));
    }
  });


  getRandomFreePort(8089)
    .then(port => {
      server.listen(port, () => {
        console.log(`分片上传服务运行中：http://${getLocalIP()}:${port}`);
        openBrowser(`http://${getLocalIP()}:${port}`)
      });
    })
    .catch(async (err) => {
      console.error('获取端口失败:', err)
      console.log('🟩 🟩 🟩 自动重试获取一个随机端口');
      let port = await getRandomFreePort()
      server.listen(port, () => {
        console.log(`自动重试后分片上传服务运行中：http://${getLocalIP()}:${port}`);
        openBrowser(`http://${getLocalIP()}:${port}`)
      });
    });


  function getRandomFreePort(specifiedPort = 0) {
    return new Promise((resolve, reject) => {
      const server = net.createServer();

      // 绑定到随机端口（0表示由系统自动分配）
      server.listen(specifiedPort, () => {
        const port = server.address().port;
        console.log('🟩 🟩 🟩 server.address()', server.address());
        server.close(() => resolve(port));
      });
      server.on('error', reject);
    });
  }



  // 获取局域网IP的函数
  function getLocalIP() {
    const interfaces = os.networkInterfaces();
    for (const name of Object.keys(interfaces)) {
      for (const iface of interfaces[name]) {
        // 过滤出IPv4、非内部（非127.0.0.1）、状态为up的地址
        if (iface.family === 'IPv4' && !iface.internal && iface.address !== '127.0.0.1' && iface.address.startsWith('192.168')) {
          return iface.address;
        }
      }
    }
    return '0.0.0.0'; // 默认回退
  }


  function openBrowser(url) {
    // 跨平台解决方案
    const openCommand = process.platform === 'win32' ? 'start' :
      (process.platform === 'darwin' ? 'open' : 'xdg-open');

    exec(`${openCommand} ${url}`, (error) => {
      if (error) {
        console.error(`无法打开浏览器: ${error}`);
      }
    });
  }
})();